<?php
namespace Emos\Db;

abstract class AbstractAdapter {
    public $connection = null;
    protected $params = array();
    protected $binds = array();
    
    public static function GetAdapter($params) {
        $adapterClass = 'Emos\\Db\\'.$params['adapter']; 
        $adapter = new $adapterClass;
        $adapter->connect($params);
        return $adapter;
    }


    public function __construct(array $params = null) {
        if($params !== null) {
            $this->connect($params);
        }        
    }


    public abstract function connect($params);
    
    public function useConnection($connection) {
        $this->connection = $connection;
    }
    
    protected abstract function selectQuery($sql);
    
    protected abstract function execQuery($sql);
    
    public abstract function getInsertId();     
    
    public function parse($sql, $params) {
        $this->params = $params;
        $this->binds = array();
        $sql = preg_replace_callback('/ \? ([a-z_]+)  (: [a-z]+)? /xi', 'Emos\Db\AbstractAdapter::paramCallback', $sql);    
        $sql2 = $this->addLimits($sql);   
        
        return $sql2;
    }   

    protected function paramCallback($maches) {   
        if(!array_key_exists($maches[1], $this->params)) {
            throw new Exception("Unknown SQL placeholder '{$maches[1]}'");    
        }
        
        $name =  $maches[1];        
        $type = @$maches[2];
        $val = @$this->params[$name];
        
        if($val === null) {
            return 'NULL';
        }
                
        if($type == ':raw') {
            return $val;
        }         
        elseif($type == ':name') {
            return $this->nameEscape($val);
        }           
        elseif($type == ':int') {
            return (int)$val;
        }
        elseif($type == ':float') {
            return (float)$val;
        }         
        elseif($type == ':list') {
            $listSql = '';
            foreach($val as $v) {
                if($listSql != '') {
                   $listSql .= ', '; 
                }
                $listSql .= "'".$this->escape($v)."'";
            }
            
            if($listSql == '') {
                $listSql = '-1';
            }
            
            return $listSql;
        }                 
        else {        
            if($val instanceof SqlExpression) {
                return (string)$val;
            }
            else {
                return $this->getParamExpression($name, $val);
            }
        }                
    }    
    
    protected function getParamExpression($name, $value) {
        return "'".$this->escape((string)$value)."'";
    }
    
    protected function addLimits($sql) {        
        if(isset($this->params['offset'], $this->params['limit'])) {            
            return $sql." LIMIT ".(int)$this->params['offset'].", ".(int)$this->params['limit'];
        }                
        elseif(isset($this->params['limit'])) {
            return $sql." LIMIT ".(int)$this->params['limit'];
        }
        else {
            return $sql;
        }
    }    

    public function escape($str) {
        return addslashes($str);    
    }    
    
    public function nameEscape($str) {
        return '`'.$str.'`';
    }    
    
    public function getSetting($name) {        
        return $this->selectVal("SELECT value FROM ?table:name WHERE name=?name", array('name'=>$name, 'table'=>'settings'));
    }

    public function getSettings($beginWith) {        
        //$beginWith = strtr($beginWith);
        return $this->selectDict("SELECT name, value FROM ?table:name WHERE name LIKE ?beginWith", null, null, array('beginWith'=>$beginWith.'%', 'table'=>'settings'));
    }    
    
	public function exec($sql, $params=array()) {	            
        $processedSql = $this->parse($sql, $params);
		return $this->execQuery($processedSql);   
	}    
    
	public function select($sql, $keyField=null, $params=array()) {	           
		$data = $this->selectQuery($this->parse($sql, $params));

        if($keyField === null) {
            return $data;
        }
        else {
            $data2 = array();
            foreach ($data as $rec) {
                $data2[$rec[$keyField]] = $rec;
            }
            return $data2;
        }        
	}
    
	public function selectDict($sql, $keyField=null, $valueField=null, $params=array()) {	    
        $data = $this->selectQuery($this->parse($sql, $params));

        if(count($data)>0 && $keyField===null && $valueField===null) {
            $fieldsNames = array_keys($data[0]);
            $keyField   = $fieldsNames[0]; 
            $valueField = $fieldsNames[1];
        }
                
		$dict = array();        
        foreach ($data as $rec) {
            $dict[$rec[$keyField]] = $rec[$valueField];
        }		        
        
		return $dict;		
	}  
    
	public function selectTree($sql, $keyField=null, $parentKeyField=null, $params=array()) {	    
        if($keyField===null && $parentKeyField===null) {
            $fieldsNames = array_keys($data[0]);
            $keyField       = $fieldsNames[0]; 
            $parentKeyField = $fieldsNames[1];
        }
        
        $data = $this->select($sql, $keyField, $params);        
                
		$tree = array();        
        foreach($data as $id => $rec) {
            if(!isset($data[$id]['children'])) {
                $data[$id]['children'] = array();
            }
            
            if($rec[$parentKeyField] === null || $rec[$parentKeyField] == 0) {
                $tree[ $id ] =& $data[ $id ];
            }
            else {
                if(isset($rec[$parentKeyField])) {
                    $data[ $rec[$parentKeyField] ]['children'][ $id ] =& $data[ $id ];                    
                }
            }
        }	        
        
		return $tree;		
	}     
    
    public function selectRow($sql, $params=array()) {
        $data = $this->selectQuery($this->parse($sql, $params));
        if(count($data) > 0) {
            return $data[0];
        }
        else {
            return array();
        }    
    }

    public function selectVal($sql, $params=array()) {
        $data = $this->selectQuery($this->parse($sql, $params));
        if(count($data) > 0) {
            $row = $data[0];            
            return current($row);
        }
        else {
            return null;
        }    
    }    
    
    /*
	public function selectCol($sql, $value, $params=array()) {	    
        $sql = $this->parse($sql, $params);
        $res = $this->query($sql);
        
		$arr = array();
		if(is_resource($res) and mysql_num_rows($res)) {
			while($rec = mysql_fetch_assoc($res)) {
				$arr[] = $rec;
			}
			mysql_free_result($res);
		    return $arr;
		}
		else {
			return array();
		}
	}    	     
    */
    
    // TODO: Add: relations, sort indexes
    public function updateDbSchema($struct) {
        $sql = array();
        foreach($struct['tables'] as $table) {
            $sql[] = 
                "CREATE TABLE ".$this->nameEscape($table['name'])." ( \n".
                "    ".$this->nameEscape($table['pk'])." BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY \n".
                ") ENGINE='InnoDB'";

            foreach($table['fkes'] as $field) {                
                $sql[] = "ALTER TABLE ".$this->nameEscape($table['name'])." CHANGE COLUMN ".$this->nameEscape($field['name'])." ".$this->nameEscape($field['name'])." BIGINT NULL";                
                $sql[] = "ALTER TABLE ".$this->nameEscape($table['name'])." ADD COLUMN ".$this->nameEscape($field['name'])." BIGINT NULL";                
                
                $sql[] = "DROP INDEX ".$this->nameEscape($table['name'])."";                
                $sql[] = "ALTER TABLE ".$this->nameEscape($table['name'])." ADD INDEX ".$this->nameEscape($field['name'].'_fk')." (".$this->nameEscape($field['name']).")";                
            }             
            
            foreach($table['fields'] as $field) {
                $null = $field['nullable'] ? "NULL" : "NOT NULL";
                $sql[] = "ALTER TABLE ".$this->nameEscape($table['name'])." CHANGE COLUMN ".$this->nameEscape($field['name'])." ".$this->nameEscape($field['name'])." {$field['type']} {$null}";                
                $sql[] = "ALTER TABLE ".$this->nameEscape($table['name'])." ADD COLUMN ".$this->nameEscape($field['name'])." {$field['type']} {$null}";                
            }            
        }
        
        foreach($sql as $cmd) {
            $fault = false;
            try {
                $this->exec($cmd);
            }
            catch(\Exception $ex) {
                $fault = true;
            }
            
            if(!$fault) {
                print "{$cmd}\n";
            }            
        }                        
    }    
     
}
